/*
 * Copyright (c) 2023 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include <mc/dbus/shm/serialize.h>
#include <mc/engine.h>
#include <mc/log.h>
#include <cstdio>

#include "hi182x_card.h"
#include "lldp/lldp.h"

namespace dev {

hi182x_card::hi182x_card() : m_mctp_object(nullptr) {
    // 初始化成员变量
}

hi182x_card::~hi182x_card() {
    // 释放MCTP对象，防止严重内存泄漏
    if (m_mctp_object) {
        delete m_mctp_object;
        m_mctp_object = nullptr;
    }
    // 取消注册OS重置回调
    m_network_adapter.unregister_os_reset_callback();
}

void hi182x_card::init_network_ports() {
    auto objects = get_children();
    if (objects.empty()) {
        elog("Error: No network ports found");
        return;
    }
    for (auto& object : objects) {
        auto object_name = object->get_name();
        if (object_name.substr(0, object_name.find_first_of("_")) == "NicPort") {
            ilog("hi182x_card init network port ${name}", ("name", object_name));
            m_network_ports.push_back(static_cast<hi182x_port*>(object.get()));
        }
    }
}

// 实现pcie_nic_card的虚函数
bool hi182x_card::start() {
    init_network_ports();
    for (auto& port : m_network_ports) {
        port->start();
    }
    start_protocol();
    start_smbus_protocol();
    setup_vdpci_lldp_listener();
    init_imu_protocol();
    // 注册OS重置回调，重新初始化ncsi任务
    m_network_adapter.register_os_reset_callback([this]() {
        this->handle_os_reset();
    });
    update_max_sfp_temperature();

    return true;
}

// 实现pcie_nic_card的虚函数
bool hi182x_card::stop() {
    return true;
}

void hi182x_card::replace_dynamic_property(mc::mutable_dict& object, const mc::dict& connector) {
    m_pcie_device.replace_dynamic_property(object, connector);
    m_board.replace_dynamic_property(object, connector);
}

// 实现pcie_nic_card的虚函数
bool hi182x_card::init(mc::mutable_dict& csr_object, const mc::dict& connector) {
    try {
        from_variant(csr_object, *this);
    } catch (const std::exception& e) {
        elog("hi182x_card init failed, exception: ${exception}", ("exception", e.what()));
        return false;
    }
    return true;
}

bool hi182x_card::start_ncsi_protocol() {
    if ((m_pcie_device.DevBus == 0) && (m_pcie_device.DevDevice == 0) &&
        (m_pcie_device.DevFunction == 0)) {
        dlog("PCIe device bdf not initialized");
        return false;
    }
    uint16_t phy_addr = mctp::init_phy_addr(m_pcie_device.DevBus, m_pcie_device.DevDevice,
                                            m_pcie_device.DevFunction);

    // 使用成员变量管理MCTP对象生命周期，防止严重内存泄漏
    m_mctp_object = new mctp(this, phy_addr, MCTP_MESSAGE_TYPE::MCTP_MESSAGE_TYPE_NCSI, "");

    auto ncsi_update_task = [this]() {
        m_ncsi_over_mctp_huawei = std::make_shared<ncsi_over_mctp_huawei>(*m_mctp_object);
        bool ret;
        for (auto& port : m_network_ports) {
            port->start_ncsi_protocol(m_ncsi_over_mctp_huawei, m_interval);
            // 开启lldp_over_mctp，接收网卡透传的lldp报文
            ret = port->enable_lldp_over_mctp();
            if (ret) {
                nlog("Success: Enabled lldp_over_mctp for ${network_adapter_name} port(${port_id})",
                     ("network_adapter_name", m_pcie_device.DeviceName),
                     ("port_id", port->m_network_port.PortId));
            } else {
                elog("Error: Failed to enable lldp_over_mctp for ${network_adapter_name} port(${port_id})",
                     ("network_adapter_name", m_pcie_device.DeviceName),
                     ("port_id", port->m_network_port.PortId));
            }
        }
        
        m_pcie_device.start_ncsi_update_task_huawei(m_ncsi_over_mctp_huawei, mc::milliseconds(120000));
        m_network_adapter.start_ncsi_update_task_huawei(m_ncsi_over_mctp_huawei, mc::milliseconds(120000));
        m_network_adapter_fault_status.start_ncsi_update_task_huawei(m_ncsi_over_mctp_huawei, m_interval);
        m_pcie_device_bandwidth.start_ncsi_update_task_huawei(m_ncsi_over_mctp_huawei, mc::milliseconds(180000));
        m_network_adapter_cooling.start_ncsi_update_task_huawei(m_ncsi_over_mctp_huawei, m_interval);
        m_network_adapter_log_collection.init_ncsi_endpoint(m_ncsi_over_mctp_huawei);
    };
    m_mctp_object->create_transport_and_endpoint(std::string(get_object_name()), ncsi_update_task);
    return true;
}

bool hi182x_card::start_protocol() {
    if (!start_ncsi_protocol()) {
        m_pcie_device.pcie_device_bdf_changed.connect(
            [this](uint8_t bus, uint8_t device, uint8_t function) {
                ilog("PCIe device bdf(${bus}:${device}:${function}) changed, start ncsi protocol",
                     ("bus", bus)("device", device)("function", function));
                start_ncsi_protocol();
            });
        return false;
    }
    return true;
}

bool hi182x_card::update_max_sfp_temperature() {
    uint16_t max_temperature = 0;
    uint8_t  status          = 2;
    for (auto& port : m_network_ports) {
        auto& om_cooling_ref = port->m_optical_module->m_optical_module_cooling;
        if ((om_cooling_ref.TemperatureCelsius != 0xFFFF) &&
            (om_cooling_ref.TemperatureCelsius > max_temperature)) {
            max_temperature = om_cooling_ref.TemperatureCelsius;
            status          = 0;
        }
    }
    m_network_adapter_cooling.update_max_sfp_temperature(max_temperature, status);
    return true;
}

// 异步更新所有端口的BDF信息
void hi182x_card::update_ports_bdf_async(imu_pcie_device_req_t& pcie_device_info) {
    for (auto& port : m_network_ports) {
        bool is_success = port->update_port_bdf(pcie_device_info);
        if (!is_success) {
            elog("Error: Failed to update port(${port_id}) BDF",
                 ("port_id", port->m_network_port.PortId.get_value().as<uint8_t>()));
        }
    }
}

void hi182x_card::register_RefChip() {
    // m_ref_chip = ref_chip;
    auto ref_chip_value = m_network_adapter.RefChip.get_value();
    if (ref_chip_value.is_extension()) {
        auto ref_obj = ref_chip_value.as<mc::engine::ref_object*>();
        if (ref_obj != nullptr) {
            m_ref_chip = mc::make_shared<mc::engine::ref_object>(*ref_obj);
        }
    }
}

bool hi182x_card::init_smbus_protocol() {
    if (m_ref_chip == nullptr) {
        elog("Error: RefChip is not registered");
        return false;
    }
    m_smbus_obj = mc::make_shared<smbus>(this);
    // 使用静态变量缓存SMBus初始化参数，减少重复创建
    static mc::mutable_dict params_template{{"BufferLen", 64}, {"McuAddress", 0xD4}};
    m_smbus_obj->init(params_template);
    m_smbus_obj->register_WriteRead_method([&](const std::vector<uint8_t>& data, uint32_t len) {
        auto result = m_ref_chip->invoke("bmc.dev.Chip", "BlockIOWriteRead", {data, len});
        if (result.is_null()) {
            elog("Error: BlockIOWriteRead failed");
            return std::vector<uint8_t>();
        }
        return result.as<std::vector<uint8_t>>();
        // elog("hi182x_card start smbus protocol in process");
        // return m_ref_chip->m_interface_chip.BlockIOWriteRead(data, len);
    });
    return true;
}

void hi182x_card::register_WriteRead_method(
    std::function<std::vector<uint8_t>(const std::vector<uint8_t>&, uint32_t)> func) {
    m_smbus_obj->register_WriteRead_method(func);
}

bool hi182x_card::start_smbus_protocol() {
    register_RefChip();
    if (!init_smbus_protocol()) {
        return false;
    }
    ilog("start smbus protocol");
    m_network_adapter.start_smbus_update_task(m_smbus_obj, mc::milliseconds(120000));
    m_network_adapter_fault_status.start_smbus_update_task(m_smbus_obj, m_smbus_interval);
    m_network_adapter_cooling.start_smbus_update_task(m_smbus_obj, m_smbus_interval);
    m_network_adapter_log_collection.init_smbus_obj(m_smbus_obj);

    for (auto& port : m_network_ports) {
        port->start_smbus_update_task(m_smbus_obj, m_smbus_interval);
    }
    return true;
}

bool hi182x_card::init_imu_protocol() {
    try {
        m_imu_obj = std::make_shared<imu>(get_service());
        ilog("IMU protocol initialized successfully");
        return true;
    } catch (const std::exception& e) {
        elog("Failed to initialize IMU protocol: ${error}", ("error", e.what()));
        return false;
    }
}

void hi182x_card::stop_ncsi_protocol() {
    nlog("hi182x_card stop ncsi protocol");
    // 停止所有组件的NCSI任务
    m_pcie_device.stop_ncsi_update_task();
    m_pcie_device_bandwidth.stop_ncsi_update_task();
    m_network_adapter.stop_ncsi_update_task();
    m_network_adapter_fault_status.stop_ncsi_update_task();
    m_network_adapter_cooling.stop_ncsi_update_task();
    m_network_adapter_log_collection.clean_ncsi_endpoint();
    // 停止所有端口的NCSI任务
    for (auto& port : m_network_ports) {
        port->stop_ncsi_protocol();
    }
    // 清空endpoint
    if (m_mctp_object) {
        delete m_mctp_object;
        m_mctp_object = nullptr;
    }
    if (m_ncsi_over_mctp_huawei) {
        m_ncsi_over_mctp_huawei = nullptr;
    }
}

void hi182x_card::handle_os_reset() {
    nlog("${name} restart ncsi protocol due to OS reset", ("name", m_pcie_device.DeviceName.get_value().as<std::string>()));
    stop_ncsi_protocol();
    start_ncsi_protocol();
    nlog("${name} restart ncsi protocol end", ("name", m_pcie_device.DeviceName.get_value().as<std::string>()));
}

void hi182x_card::init_vdpci_listener() {
    uint16_t phy_addr = mctp::init_phy_addr(m_pcie_device.DevBus, m_pcie_device.DevDevice,
                                            m_pcie_device.DevFunction);

    m_vdpci_mctp_obj =
        mc::make_shared<mctp>(this, phy_addr, MCTP_MESSAGE_TYPE::MCTP_MESSAGE_TYPE_VDPCI, "");

    m_vdpci_mctp_obj->create_transport_and_endpoint(std::string(get_object_name()), [this]() {
        auto service = get_service();
        if (!service) {
            elog("Service not available for LLDP listener setup");
            return;
        }

        std::string endpoint_path = m_vdpci_mctp_obj->get_endpoint_path();
        if (endpoint_path.empty()) {
            elog("Failed to get MCTP endpoint path for LLDP listener");
            return;
        }

        mc::dbus::match_rule rule = mc::dbus::match_rule::new_signal(
            "MessageReceived", "bmc.kepler.Systems.Mctp.PCIeEndpoint");
        rule.with_path(endpoint_path);

        service->add_match(rule, [this, service](mc::dbus::message& msg) {
            service->post([this, msg = std::move(msg)]() mutable {
                handle_lldp_message(msg);
            });
        });
        nlog("${network_adapter_name} VDPCI LLDP listener setup completed successfully",
             ("network_adapter_name", m_pcie_device.DeviceName));
    });
}

void hi182x_card::setup_vdpci_lldp_listener() {
    try {
        // 检查PCIe设备BDF是否已初始化
        if (m_pcie_device.DevBus == 0 && m_pcie_device.DevDevice == 0 &&
            m_pcie_device.DevFunction == 0) {
            nlog("PCIe device BDF not initialized, will setup LLDP listener later");
            // 监听BDF变化，当BDF初始化后再设置LLDP监听器
            m_pcie_device.pcie_device_bdf_changed.connect(
                [this](uint8_t bus, uint8_t device, uint8_t function) {
                    nlog("PCIe device BDF changed, setting up VDPCI LLDP listener");
                    setup_vdpci_lldp_listener();
                });
            return;
        }
        init_vdpci_listener();
    } catch (const std::exception& e) {
        elog("${network_adapter_name} Failed to initialize VDPCI LLDP listener: ${error}",
             ("network_adapter_name", m_pcie_device.DeviceName), ("error", e.what()));
    }
}

void hi182x_card::log_lldp_message_data_hex(const std::vector<uint8_t>& data) const {
    if (data.empty()) {
        dlog("${network_adapter_name} message_data is empty",
             ("network_adapter_name", m_pcie_device.DeviceName));
        return;
    }
    size_t buffer_size = data.size() * 3 + 1;
    std::vector<char> hex_buffer(buffer_size, '\0');
    size_t offset = 0;
    for (size_t i = 0; i < data.size(); ++i) {
        int written = std::snprintf(hex_buffer.data() + offset, buffer_size - offset, "%02X",
                                    static_cast<unsigned int>(data[i]));
        if (written <= 0 || static_cast<size_t>(written) >= buffer_size - offset) {
            break;
        }
        offset += static_cast<size_t>(written);
        if (i < data.size() - 1) {
            hex_buffer[offset++] = ' ';
        }
    }
    const std::string hex_data(hex_buffer.data(), offset);
    dlog("${network_adapter_name} message_data (${size} bytes): ${hex_data}",
         ("network_adapter_name", m_pcie_device.DeviceName), ("size", data.size()),
         ("hex_data", hex_data));
}

void hi182x_card::process_lldp_payload(const mc::dict& lldp_rsp) {
    mc::mutable_dict parsed_data;
    if (lldp_rsp.contains("tlvs") && lldp_rsp["tlvs"].is_array()) {
        auto tlvs_array = lldp_rsp["tlvs"].as_array();
        for (const auto& tlv_variant : tlvs_array) {
            if (!tlv_variant.is_dict()) {
                continue;
            }
            auto tlv_dict = tlv_variant.as_dict();
            if (!tlv_dict.contains("tlv_type") || !tlv_dict["tlv_type"].is_numeric()) {
                continue;
            }
            if (!tlv_dict.contains("tlv_info_string") || !tlv_dict["tlv_info_string"].is_string()) {
                continue;
            }

            lldp_tlv tlv;
            tlv.type = static_cast<tlv_type>(tlv_dict["tlv_type"].as<uint8_t>());
            tlv.tlv_info_string = tlv_dict["tlv_info_string"].as_string();

            try {
                auto tlv_parsed = m_lldp_parser.parse_lldp_tlv(tlv);
                for (const auto& entry : tlv_parsed) {
                    if (!entry.key.is_string()) {
                        continue;
                    }
                    parsed_data[entry.key.as_string()] = entry.value;
                }
            } catch (const std::exception& e) {
                dlog("${network_adapter_name} Unable to parse LLDP TLV, error: ${error}",
                     ("network_adapter_name", m_pcie_device.DeviceName), ("error", e.what()));
            }
        }
    }

    uint8_t port_id = 0;
    if (lldp_rsp.contains("port_id") && lldp_rsp["port_id"].is_numeric()) {
        port_id = lldp_rsp["port_id"].as<uint8_t>();
    }

    for (auto& port : m_network_ports) {
        if (port && port->m_network_port.PortId.get_value().as<uint8_t>() == port_id) {
            port->update_lldp_receive(lldp_rsp, parsed_data);
            ilog("${network_adapter_name} Updated LLDP receive info for port ${port_id}",
                 ("network_adapter_name", m_pcie_device.DeviceName), ("port_id", port_id));
            return;
        }
    }
    dlog("${network_adapter_name} Port ${port_id} not found for LLDP update",
         ("network_adapter_name", m_pcie_device.DeviceName), ("port_id", port_id));
}

void hi182x_card::handle_lldp_message(mc::dbus::message& msg) {
    try {
        auto                 reader = msg.reader();
        mc::dict             ctx;
        std::vector<uint8_t> message_data;
        reader >> ctx >> message_data;

        log_lldp_message_data_hex(message_data);

        auto lldp_rsp = m_lldp_parser.parse(message_data);
        if (lldp_rsp.empty()) {
            dlog("${network_adapter_name} Unable to parse VDPCI LLDP message",
                 ("network_adapter_name", m_pcie_device.DeviceName));
            return;
        }

        process_lldp_payload(lldp_rsp);
    } catch (const std::exception& e) {
        dlog("${network_adapter_name} Failed to handle LLDP message: ${error}",
             ("network_adapter_name", m_pcie_device.DeviceName), ("error", e.what()));
    }
}

} // namespace dev

MC_REFLECT(dev::hi182x_card,
           ((m_system_id, "SystemId"))((m_pcie_device, "bmc.dev.PCIeDevice"))(
               (m_pcie_card, "bmc.dev.PCIeCard"))((m_network_adapter, "bmc.dev.NetworkAdapter"))(
               (m_network_adapter_fault_status, "bmc.dev.NetworkAdapter.FaultStatus"))(
               (m_network_adapter_cooling, "bmc.dev.NetworkAdapter.Cooling"))(
               (m_board, "bmc.dev.Board"))((m_pcie_device_bandwidth,
                                            "bmc.dev.PCIeDevice.Bandwidth"))(
               (m_network_adapter_log_collection, "bmc.dev.NetworkAdapter.LogCollection")))
